/*
 * Copyright (C) 2010 Fores Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jp.fores.midori.xmlrpc;

import java.util.ArrayList;
import java.util.List;

import javax.xml.namespace.QName;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;


/**
 * XML-RPCのリクエストデータの構文解析を行うクラス。<br>
 * (このクラスはスレッドセーフではありません。)<br>
 * <br>
 * このクラスは、通常のXML-RPCとApache XML-RPCの両方に対応しています。<br>
 */
public class XmlRpcRequestParser extends AbstractRecursiveTypeParserImpl {
	//==========================================================
	//フィールド

	/**
	 * 「methodName」タグの内容を格納するためのStringBuilder
	 */
	private StringBuilder methodNameSb = null;

	/**
	 * 「params」タグの処理結果のオブジェクトを格納するためのList
	 */
	private List<Object> paramsList = null;

	/**
	 * 「methodName」タグの処理中かどうかのフラグ
	 */
	private boolean inMethodNameTag = false;

	/**
	 * 「params」タグの処理中かどうかのフラグ
	 */
	private boolean inParamsTag = false;

	/**
	 * 「methodName」タグの処理が完了したかどうかのフラグ
	 */
	private boolean doneMethodNameTag = false;

	/**
	 * 「params」タグの処理が完了したかどうかのフラグ
	 */
	private boolean doneParamsTag = false;

	/**
	 * 「value」タグの処理が完了したかどうかのフラグ
	 */
	private boolean doneValueTag = false;


	//==========================================================
	//メソッド

	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//抽象メソッドの実装

	/**
	 * 結果のオブジェクトを追加します。<br>
	 *
	 * @param obj 結果のオブジェクト
	 * @throws SAXException SAX例外
	 */
	@Override
	protected void addResult(Object obj) throws SAXException {
		//引数のオブジェクトを、フィールドの「params」タグの処理結果のオブジェクトを格納するためのListに追加する
		this.paramsList.add(obj);
	}


	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//オーバーライドするメソッド

	/**
	 * 要素の開始通知を受け取ります。<br>
	 *
	 * @param uri 名前空間URI
	 * @param localName 前置修飾子を含まないローカル名
	 * @param qName 接頭辞を持つ修飾名
	 * @param attributes 要素に付加された属性
	 * @throws SAXException SAX例外
	 */
	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		//タグの階層レベルを+1する
		this.level++;

		//階層レベルが1の場合
		if (this.level == 1) {
			//名前空間URIの指定無しの「methodCall」タグ以外の場合
			if (!(isBlank(uri) && "methodCall".equals(localName))) {
				//例外を投げる
				throw new SAXParseException("「methodCall」タグが開始されるべきところで、別のタグが開始されました。"
						+ new QName(uri, localName), getDocumentLocator());
			}
		}
		//階層レベルが2の場合
		//(この階層には「methodName」タグと「params」タグがそれぞれ1つずつ含まれていなければならない)
		else if (this.level == 2) {
			//名前空間URIの指定無しの「methodName」タグ、または「params」タグ以外の場合
			if (!(isBlank(uri) && ("methodName".equals(localName) || "params".equals(localName)))) {
				//例外を投げる
				throw new SAXParseException("「methodName」タグ、または「params」タグが開始されるべきところで、別のタグが開始されました。"
						+ new QName(uri, localName), getDocumentLocator());
			}

			//「methodName」タグの場合
			if ("methodName".equals(localName)) {
				//「methodName」タグの処理が完了したかどうかのフラグがたっている場合
				//(同じ「methodCall」タグの中で、すでに別の「methodName」タグが開始されていた場合)
				if (this.doneMethodNameTag) {
					//例外を投げる
					throw new SAXParseException("1つの「methodCall」タグの中に、複数の「methodName」タグを含めることはできません。"
							+ new QName(uri, localName), getDocumentLocator());
				}

				//「methodName」タグの処理中かどうかのフラグをたてる
				this.inMethodNameTag = true;

				//「methodName」タグの内容を格納するためのStringBuilderのインスタンスを生成
				this.methodNameSb = new StringBuilder();
			}
			//「params」タグの場合
			else if ("params".equals(localName)) {
				//「params」タグの処理が完了したかどうかのフラグがたっている場合
				//(同じ「methodCall」タグの中で、すでに別の「params」タグが開始されていた場合)
				if (this.doneParamsTag) {
					//例外を投げる
					throw new SAXParseException("1つの「methodCall」タグの中に、複数の「params」タグを含めることはできません。"
							+ new QName(uri, localName), getDocumentLocator());
				}

				//「params」タグの処理中かどうかのフラグをたてる
				this.inParamsTag = true;

				//「params」タグの処理結果のオブジェクトを格納するためのListのインスタンスを生成
				this.paramsList = new ArrayList<Object>();
			}
		}
		//階層レベルが3の場合
		else if (this.level == 3) {
			//名前空間URIの指定無しの「param」タグ以外の場合
			if (!(isBlank(uri) && "param".equals(localName))) {
				//例外を投げる
				throw new SAXParseException("「param」タグが開始されるべきところで、別のタグが開始されました。"
						+ new QName(uri, localName), getDocumentLocator());
			}
		}
		//階層レベルが4の場合
		else if (this.level == 4) {
			//名前空間URIの指定無しの「value」タグ以外の場合
			if (!(isBlank(uri) && "value".equals(localName))) {
				//例外を投げる
				throw new SAXParseException("「value」タグが開始されるべきところで、別のタグが開始されました。"
						+ new QName(uri, localName), getDocumentLocator());
			}

			//「value」タグの処理が完了したかどうかのフラグをたっている場合
			if (this.doneValueTag) {
				//例外を投げる
				throw new SAXParseException("1つの「param」タグの中に、複数の「value」タグを含めることはできません。", getDocumentLocator());
			}

			//「value」タグの開始時の処理を呼び出す
			startValueTag();
		}
		//それ以外の場合
		else {
			//親クラスの処理を呼び出す
			super.startElement(uri, localName, qName, attributes);
		}
	}

	/**
	 * 要素の終了通知を受け取ります。<br>
	 *
	 * @param uri 名前空間URI
	 * @param localName 前置修飾子を含まないローカル名
	 * @param qName 接頭辞を持つ修飾名
	 * @throws SAXException SAX例外
	 */
	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		//階層レベルが1の場合
		if (this.level == 1) {
			//名前空間URIの指定無しの「methodCall」タグ以外の場合
			if (!(isBlank(uri) && "methodCall".equals(localName))) {
				//例外を投げる
				throw new SAXParseException("「methodCall」タグが終了されるべきところで、別のタグが終了されました。"
						+ new QName(uri, localName), getDocumentLocator());
			}

			//「methodName」タグの処理が完了したかどうかのフラグがたっていない場合
			if (!this.doneMethodNameTag) {
				//例外を投げる
				throw new SAXParseException("「methodCall」タグの中に「methodName」タグが含まれていませんでした。"
						+ new QName(uri, localName), getDocumentLocator());
			}

			//「params」タグの処理が完了したかどうかのフラグがたっていない場合
			if (!this.doneParamsTag) {
				//例外を投げる
				throw new SAXParseException("「methodCall」タグの中に「params」タグが含まれていませんでした。"
						+ new QName(uri, localName), getDocumentLocator());
			}
		}
		//階層レベルが2の場合
		//(この階層には「methodName」タグと「params」タグがそれぞれ1つずつ含まれていなければならない)
		else if (this.level == 2) {
			//名前空間URIの指定無しの「methodName」タグで、かつ「methodName」タグの処理中かどうかのフラグがたっている場合
			if (isBlank(uri) && "methodName".equals(localName)
					&& this.inMethodNameTag) {
				//「methodName」タグの処理中かどうかのフラグをfalseに戻す
				this.inMethodNameTag = false;

				//「methodName」タグの処理が完了したかどうかのフラグをたてる
				this.doneMethodNameTag = true;
			}
			//名前空間URIの指定無しの「params」タグで、かつ「params」タグの処理中かどうかのフラグがたっている場合
			else if (isBlank(uri) && "params".equals(localName)
					&& this.inParamsTag) {
				//「params」タグの処理中かどうかのフラグをfalseに戻す
				this.inParamsTag = false;

				//「params」タグの処理が完了したかどうかのフラグをたてる
				this.doneParamsTag = true;
			}
			//それ以外の場合
			else {
				//ここに到達するのは不正な場合なので、例外を投げる
				throw new SAXParseException("「タグの階層構造が不正です。"
						+ new QName(uri, localName), getDocumentLocator());
			}
		}
		//階層レベルが3の場合
		else if (this.level == 3) {
			//名前空間URIの指定無しの「param」タグ以外の場合
			if (!(isBlank(uri) && "param".equals(localName))) {
				//例外を投げる
				throw new SAXParseException("「param」タグが終了されるべきところで、別のタグが終了されました。"
						+ new QName(uri, localName), getDocumentLocator());
			}

			//「value」タグの処理が完了したかどうかのフラグをfalseに戻す
			this.doneValueTag = false;
		}
		//階層レベルが4の場合
		else if (this.level == 4) {
			//名前空間URIの指定無しの「value」タグ以外の場合
			if (!(isBlank(uri) && "value".equals(localName))) {
				//例外を投げる
				throw new SAXParseException("「value」タグが終了されるべきところで、別のタグが終了されました。"
						+ new QName(uri, localName), getDocumentLocator());
			}

			//「value」タグの終了時の処理を呼び出す
			endValueTag();

			//「value」タグの処理が完了したかどうかのフラグをたてる
			this.doneValueTag = true;
		}
		//それ以外の場合
		else {
			//親クラスの処理を呼び出す
			super.endElement(uri, localName, qName);
		}

		//タグの階層レベルを-1する
		this.level--;
	}

	/**
	 * 要素内の文字データの通知を受け取ります。<br>
	 * このクラスの実装では、文字配列の使用範囲に空白文字以外が含まれている場合は例外を投げます。<br>
	 *
	 * @param ch 文字配列
	 * @param start 文字配列内の開始位置
	 * @param length 文字配列から使用される文字数
	 * @throws SAXException SAX例外
	 */
	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		//(「methodName」タグの処理中の場合)
		if (this.inMethodNameTag) {
			//文字配列の使用範囲を「methodName」タグの内容を格納するためのStringBuilderに追加する
			this.methodNameSb.append(ch, start, length);
		}
		//それ以外の場合
		else {
			//親クラスの処理を呼び出す
			super.characters(ch, start, length);
		}
	}


	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//Getter

	/**
	 * 「methodName」タグの内容の文字列を取得します。<br>
	 *
	 * @return 「methodName」タグの内容の文字列
	 */
	public String getMethodName() {
		//フィールドの「methodName」タグの内容を格納するためのStringBuilderを文字列に変換して返す
		return this.methodNameSb.toString();
	}

	/**
	 * 「params」タグの処理結果のオブジェクトの配列を取得します。<br>
	 *
	 * @return 「params」タグの処理結果のオブジェクトの配列
	 */
	public Object[] getParams() {
		//フィールドの「params」タグの処理結果のオブジェクトを格納するためのListをオブジェクトの配列に変換して返す
		return this.paramsList.toArray(new Object[0]);
	}
}
